#include "client_pico.h"
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <net/if.h>

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <ev.h>

#define SERVER_IP "192.168.35.212" // 服务器 IP，替换为实际值
#define SERVER_IF_NAME "enp2s0"
#define SERVER_PORT 6015
#define CLIENT_PORT 6014
#define BUFFER_SIZE 1536

struct ClientContext {
    int sockfd;
    struct sockaddr_in server_addr;
    struct sockaddr_in local_addr;
    ev_io io_watcher;
    ev_timer timer_watcher;
    picoquic_quic_t* quic = NULL;
    picoquic_cnx_t* cnx = NULL;
    
};
char const* ticket_store_filename = NULL;
char const* token_store_filename = NULL;
ClientContext ctx;


typedef struct st_sample_client_stream_ctx_t {
    struct st_sample_client_stream_ctx_t* next_stream;
    size_t file_rank;
    uint64_t stream_id;
    size_t name_length;
    size_t name_sent_length;
    FILE* F;
    size_t bytes_received;
    uint64_t remote_error;
    unsigned int is_name_sent : 1;
    unsigned int is_file_open : 1;
    unsigned int is_stream_reset : 1;
    unsigned int is_stream_finished : 1;
} sample_client_stream_ctx_t;

static int get_recv_buffer(void *recv_buffer, size_t bytes_recv)
{
    if (bytes_recv <= 0)
    {
        printf("get_recv_buffer error\n");
        return 1;
    }

    uint64_t current_time = picoquic_current_time();
    int ret = picoquic_incoming_packet(ctx.quic, (uint8_t *)recv_buffer, (size_t)bytes_recv, (struct sockaddr *)&(ctx.server_addr), (struct sockaddr *)&(ctx.local_addr), 0, 0, current_time);
    if (ret != 0)
    {
        printf("picoquic_incoming_packet error!\n");
        return 1;
    }

    return 0;
}

static int get_send_buffer(void* send_buffer_, size_t& send_length_)
{
    int if_index = -2;
    size_t send_length = 0;
    
    size_t* send_msg_ptr = nullptr;
    picoquic_connection_id_t log_cid;
    struct sockaddr_storage peer_addr;
    struct sockaddr_storage local_addr;
    picoquic_cnx_t* last_cnx = nullptr;
    uint64_t current_time = picoquic_current_time();
    int ret = picoquic_prepare_next_packet_ex(ctx.quic, current_time, (uint8_t *)send_buffer_, 1536, &send_length, &peer_addr, &local_addr, &if_index, &log_cid, &last_cnx, send_msg_ptr);
    if(ret != 0)
    {
        printf("picoquic_prepare_next_packet_ex error!\n");
        return 1;
    }
    send_length_ = send_length;
    return 0;

}

// 接收数据的回调函数
static void recv_cb(EV_P_ ev_io *w, int revents) {
    ClientContext *ctx = static_cast<ClientContext*>(w->data);
    char buffer[BUFFER_SIZE];
    struct sockaddr_in from_addr;
    socklen_t addr_len = sizeof(from_addr);

    ssize_t len = recvfrom(ctx->sockfd, buffer, BUFFER_SIZE, 0,
                           (struct sockaddr*)&from_addr, &addr_len);
    if (len < 0) {
        if (errno == EAGAIN || errno == EWOULDBLOCK) return; // 暂无数据
        perror("recvfrom failed");
        ev_break(EV_A_ EVBREAK_ALL);//TODO:
        return;
    }
    //--
    int ret = get_recv_buffer(buffer, len);
    if(ret != 0)
    {
        printf("get_recv_buffer error\n");
    }
    //++
    // buffer[len] = '\0';
    std::cout << "Received from server length: " << len << std::endl;
}

// 发送数据的回调函数（定时器触发）
static void send_cb(EV_P_ ev_timer *w, int revents) {
    ClientContext *ctx = static_cast<ClientContext*>(w->data);
    std::string message = "Hello from client at " + std::to_string(time(nullptr));
    ssize_t sent = 0;
    //--
    uint8_t send_buffer_[1536];
    size_t send_length_ = 0;
    int ret = get_send_buffer(send_buffer_, send_length_);
    if (ret != 0)
    {
        printf("get_send_buffer error ret:%d send_length_:%d \n", ret , send_length_);
        return;
        // exit(1);
    }
    if(send_length_ == 0) return;
    sent = sendto(ctx->sockfd, send_buffer_, send_length_, 0,
                          (struct sockaddr *)&ctx->server_addr, sizeof(ctx->server_addr));
    //++
    // ssize_t sent = sendto(ctx->sockfd, message.c_str(), message.size(), 0,
    //                       (struct sockaddr*)&ctx->server_addr, sizeof(ctx->server_addr));
    if (sent < 0) {
        perror("sendto failed");
        ev_break(EV_A_ EVBREAK_ALL);
        return;
    }
    std::cout << "Sent to server lenth: " << sent << std::endl;
}

static int open_socket()
{
        // 创建 UDP socket
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (sockfd < 0) {
            perror("socket creation failed");
            return 1;
        }
    
        // 设置 socket 为非阻塞
        int flags = fcntl(sockfd, F_GETFL, 0);
        fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    
        // 绑定到本地 enp2s0 网卡
        struct sockaddr_in local_addr;
        memset(&local_addr, 0, sizeof(local_addr));
        local_addr.sin_family = AF_INET;
        local_addr.sin_addr.s_addr = INADDR_ANY; // 绑定任意 IP
        local_addr.sin_port = htons(CLIENT_PORT);
    
        struct ifreq ifr;
        strncpy(ifr.ifr_name, SERVER_IF_NAME, IFNAMSIZ-1);
        if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, (char*)&ifr, sizeof(ifr)) < 0) {
            perror("SO_BINDTODEVICE failed");
            close(sockfd);
            return 1;
        }
    
        if (bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
            perror("bind failed");
            close(sockfd);
            return 1;
        }
        ctx.local_addr = local_addr;
    
        // 设置服务器地址
        struct sockaddr_in server_addr;
        memset(&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(SERVER_PORT);
        if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) {
            perror("inet_pton failed");
            close(sockfd);
            return 1;
        }
    
        ctx.sockfd = sockfd;
        ctx.server_addr = server_addr;
        return 0;
}

int sample_client_callback(picoquic_cnx_t *cnx,
                           uint64_t stream_id, uint8_t *bytes, size_t length,
                           picoquic_call_back_event_t fin_or_event, void *callback_ctx, void *v_stream_ctx)
{
    switch (fin_or_event)
    {
    case picoquic_callback_stream_data:
    case picoquic_callback_stream_fin:
    {
        printf("--picoquic_callback_stream_data--\n");
    }
        break;
    case picoquic_callback_stop_sending: /* Should not happen, treated as reset */
    {
        printf("--picoquic_callback_stop_sending--\n");
        picoquic_reset_stream(cnx, stream_id, 0);
    }
    case picoquic_callback_stream_reset: /* Server reset stream #x */
    {
        printf("--picoquic_callback_stream_reset--\n");
    }
        break;
    case picoquic_callback_stateless_reset:
    {
        printf("--picoquic_callback_stateless_reset--\n");
    }
    case picoquic_callback_close: /* Received connection close */
    {
        printf("--picoquic_callback_close--\n");
    }
    case picoquic_callback_application_close: /* Received application close */
    {
        printf("--picoquic_callback_application_close--\n");
        picoquic_set_callback(cnx, nullptr, nullptr);
        // picoquic_close(cnx, PICOQUIC_ERROR_MEMORY);
        picoquic_free(ctx.quic);//ctx.cnx也会在这里被释放
    }
        break;
    case picoquic_callback_version_negotiation:
                /* The client did not get the right version.
             * TODO: some form of negotiation?
             */
            fprintf(stdout, "Received a version negotiation request:");
            for (size_t byte_index = 0; byte_index + 4 <= length; byte_index += 4) {
                uint32_t vn = 0;
                for (int i = 0; i < 4; i++) {
                    vn <<= 8;
                    vn += bytes[byte_index + i];
                }
                fprintf(stdout, "%s%08x", (byte_index == 0) ? " " : ", ", vn);
            }
            fprintf(stdout, "\n");
            break;
    case picoquic_callback_stream_gap:
        break;
    case picoquic_callback_prepare_to_send:
    {
        printf("--picoquic_callback_prepare_to_send--\n");
    }
        break;

    case picoquic_callback_almost_ready:
    {
        printf("--picoquic_callback_prepare_to_send--\n");
    }
        break;

    case picoquic_callback_ready:
    {
        printf("--picoquic_callback_prepare_to_send--\n");
    }
        break;
    default:
        printf("--picoquic_callback_default--\n");
        /* unexpected -- just ignore. */
        break;
    }
    return 0;
}

static int init_picoquic()
{
    ticket_store_filename = PICOQUIC_SAMPLE_CLIENT_TICKET_STORE;
    uint64_t current_time = picoquic_current_time();
    ctx.quic = picoquic_create(1, NULL, NULL, NULL, PICOQUIC_SAMPLE_ALPN, NULL, NULL,
                               NULL, NULL, NULL, current_time, NULL,
                               ticket_store_filename, NULL, 0);
    if (ctx.quic == NULL)
    {
        fprintf(stderr, "Could not create quic context\n");
        return 1;
    }
    else{
        token_store_filename = PICOQUIC_SAMPLE_CLIENT_TOKEN_STORE;
        if (picoquic_load_retry_tokens(ctx.quic, token_store_filename) != 0) {
            fprintf(stderr, "No token file present. Will create one as <%s>.\n", token_store_filename);
        }
        picoquic_set_default_congestion_algorithm(ctx.quic, picoquic_bbr_algorithm);
        char const *qlog_dir = PICOQUIC_SAMPLE_CLIENT_QLOG_DIR;
        picoquic_set_key_log_file_from_env(ctx.quic);
        picoquic_set_qlog(ctx.quic, qlog_dir);
        picoquic_set_log_level(ctx.quic, 1);
        picoquic_set_cipher_suite(ctx.quic, 0);//default
    }
    char const* sni = PICOQUIC_SAMPLE_SNI;
    //TODO:picoquic_null_connection_id
    ctx.cnx = picoquic_create_cnx(ctx.quic, picoquic_null_connection_id, picoquic_null_connection_id,
                                  (struct sockaddr *)&(ctx.server_addr), current_time, 0, sni, PICOQUIC_SAMPLE_ALPN, 1);
    if (ctx.cnx == NULL)
    {
        fprintf(stderr, "Could not create connection context\n");
        return 1;
    }
    picoquic_set_callback(ctx.cnx, sample_client_callback, &ctx);
    int ret = picoquic_start_client_cnx(ctx.cnx);
    if (ret < 0) {
        fprintf(stderr, "Could not activate connection\n");
        return 1;
    }
     picoquic_enable_keep_alive(ctx.cnx, 100000); 
    ret = picoquic_mark_active_stream(ctx.cnx, 4, 1, &ctx);
    if(ret != 0)
    {
        printf("picoquic_mark_active_stream error!\n");
        return 1;
    }
    return 0;
}




int main() {

    if(open_socket() != 0)
    {
        printf("error! open_socket!\n");
        exit(1);
    }
    init_picoquic();

 


    // 初始化 libev 事件循环
    struct ev_loop *loop = EV_DEFAULT;
    // 设置接收数据的 watcher
    ev_io_init(&ctx.io_watcher, recv_cb, ctx.sockfd, EV_READ);
    ctx.io_watcher.data = &ctx;
    ev_io_start(loop, &ctx.io_watcher);

    // 设置定时器，每 100ms 发送一次
    ev_timer_init(&ctx.timer_watcher, send_cb, 0.1, 0.1); // 初次延迟 100ms，之后每 100ms
    ctx.timer_watcher.data = &ctx;
    ev_timer_start(loop, &ctx.timer_watcher);

    std::cout << "Client started, bound to enp2s0, sending to " << SERVER_IP << ":" << SERVER_PORT << std::endl;

    // 运行事件循环
    ev_run(loop, 0);

    // 清理
    //TODO:清理loop
    /*
        // Save tickets and tokens, and free the QUIC context 
        if (ctx.quic != NULL) {
            if (picoquic_save_session_tickets(ctx.quic, ticket_store_filename) != 0) {
                fprintf(stderr, "Could not store the saved session tickets.\n");
            }
            
            if (picoquic_save_retry_tokens(ctx.quic, token_store_filename) != 0) {
                fprintf(stderr, "Could not save tokens to <%s>.\n", token_store_filename);
            }
            picoquic_free(ctx.quic);
        }
    */
    close(ctx.sockfd);
    picoquic_free(ctx.quic);//ctx.cnx也会在这里被释放
    return 0;
}

































